#!/usr/bin/env python3
# encoding=utf-8
# ============================================================================
# @brief    packet files
# ============================================================================

import os
import sys
import base64
import struct
import json
import uuid
import requests
from datetime import datetime

# BURNTOOL = os.path.realpath("F:\\offlinesoftware\\debug\\Realtek\\mpcli_v1.0.4.5_Windows\\mpcli.exe")
PRO_DIR = os.path.dirname(sys.argv[0])
OUTPUT_BIN = os.path.join(PRO_DIR, 'output')
os.makedirs(OUTPUT_BIN, exist_ok=True)
mfiurl = 'https://swa.apple.com'
key_file = os.path.join(r"H:\work\project\watch\findmy\CSR\myprivatekey_pkcs1.key")
cert_file = os.path.join(r"H:\work\project\watch\findmy\CSR\MFi-EmVWjUDuTWetAy3rJ75MpQ.cert.pem")

import logging
import logging.handlers
logger = logging.getLogger(__name__)
def init_logging():
    root_logger = logging.getLogger()
    root_logger.setLevel(logging.INFO)
    
    formatter = logging.Formatter("%(asctime)s - %(name)s-%(lineno)s - %(levelname)s - %(message)s")
    # 文件处理器（按天滚动，保留7天日志）
    file_handler = logging.handlers.TimedRotatingFileHandler(
        os.path.join(OUTPUT_BIN, "token.log"),
        when="midnight",  # 每天午夜滚动
        interval=1,
        backupCount=7,
        encoding="utf-8"
    )
    # 文件Handler
    # file_handler = logging.FileHandler("mailapp.log", encoding='utf=8', mode='a')
    file_handler.setFormatter(formatter)
    root_logger.addHandler(file_handler)
    
    # 控制台Handler（可选）
    console_handler = logging.StreamHandler()
    console_handler.setFormatter(formatter)
    root_logger.addHandler(console_handler)
    
# print (PKG_OUT_DIR)
def base64_2_bin(uuid, base64code, num=0, filepath=None):
    binstr = base64.b64decode(base64code) #.hex()
    if filepath and os.path.exists(filepath) :
        uuidbin = os.path.join(os.path.dirname(filepath), str(num)+'_'+uuid+'.bin')
    else :
        uuidbin = os.path.join(OUTPUT_BIN, str(num)+'_'+uuid+'.bin')
    uuid = str(uuid).replace('-', '')
    data = 0
    binfile = open(uuidbin, 'wb')
    while (data<32) :
        databyte = int(uuid[data:data+2], base=16)
        databyte = struct.pack('B', databyte)
        binfile.write(databyte)
        data = data + 2

    for data in binstr :
        databyte = struct.pack('B', data)
        binfile.write(databyte)

    pos = binfile.tell()
    while (pos < 1040) :
        data = int('0xff', base=16)
        databyte = struct.pack('B', data)
        binfile.write(databyte)
        pos = pos + 1

    logger.info(uuidbin)
    binfile.close()

def create_token_by_csv(csvfilepath):
    uidpos = 2
    tokenpos = 3
    with open(csvfilepath, "r") as csvfile:
        csvline = csvfile.readline()
        data = csvline.split(',')
        # print(data)
        if 'Uuid' in data:
            for pos,one in enumerate(data):
                if one == 'Uuid':
                    uidpos = pos
                elif one == 'Base64':
                    tokenpos = pos
            csvline = csvfile.readline()
            
        num = 1
        # print(f'Uuid,Base64 {uidpos, tokenpos}')
        while (csvline):
            data = csvline.split(',')
            base64_2_bin(data[uidpos], data[tokenpos], num, csvfilepath)
            num = num + 1
            csvline = csvfile.readline()

def print_token_bin(binfile, type='c'):
    endbytes = b'\xff' * 16
    if type == 'c':
        with open(binfile, "rb") as file:
            while True:
                chunk = file.read(16)
                if not chunk:
                    break
                if endbytes == chunk:
                    break
                for one in chunk:
                    print("0x%02X,"%one, end=' ')
                print('')
    else:
        with open(binfile, "rb") as file:
            num = 0
            while True:
                chunk = file.read(16)
                if not chunk:
                    break
                if endbytes == chunk:
                    print(f' len {num<<1}')
                    break
                num = num + 16
                for one in chunk:
                    print("%02X"%one, end='');

def mfi_api_http_get(url, headers, filepath=None):
    response = requests.get(
        url,
        headers=headers,
        cert=(cert_file, key_file),
        verify=True      # 默认验证服务器证书（需系统信任CA）
    )
    logger.info(f"get 响应状态：{response.status_code}, 结果：{response.text}")
    return (response.status_code, response.text)
    
def mfi_api_http_post(url, header, body):
    # post 响应状态：202, 结果：{"request_id":"f9c855c8-2310-11f0-bfb9-ab84109023df","download_availability":"2025-04-27 03:40:26.0"}
    # return (202, {"request_id":"f9c855c8-2310-11f0-bfb9-ab84109023df","download_availability":"2025-04-27 03:40:26.0"})
    response = requests.post(
        url,
        data=json.dumps(body),
        headers=header,
        cert=(cert_file, key_file),
        verify=True      # 默认验证服务器证书（需系统信任CA）
    )
    logger.info(f"post 响应状态：{response.status_code}, 结果：{response.text}")
    return (response.status_code, response.text)

def mfi_auth_entities_request(ppid, num):
    url = f'{mfiurl}/api/v1.0/external/authEntityRequests'
    headers = {"Accept": "application/json", "Content-Type": "application/json"}
    body = {}
    body['ppid'] = ppid
    body['requested_auth_entity_count'] = int(num)
    logger.info(body)
    # return "d63b9b32-2333-11f0-ab63-79714135aa24"
    code = 202
    # response = {"request_id":"d63b9b32-2333-11f0-ab63-79714135aa24","download_availability":"2025-04-27 07:49:59.0"}
    code, response = mfi_api_http_post(url, headers, body)
    if code != 202:
        return None
    response =  json.loads(response)
    if 'request_id' not in response:
        return None
    return response['request_id']
    # post 响应状态：202, 结果：{"request_id":"f9c855c8-2310-11f0-bfb9-ab84109023df","download_availability":"2025-04-27 03:40:26.0"}

def mfi_file_name_request(requestid):
    url = f'{mfiurl}/api/v1.0/external/authEntities/{requestid}'
    headers = {"Accept": "application/json", "Content-Type": "application/json"}
    logger.info(url)
    # return (1, ["f9c855c8-2310-11f0-bfb9-ab84109023df_1745721626669_PART1.csv"])
    code, response = mfi_api_http_get(url, headers)
    if code != 200:
        return None        
    response =  json.loads(response)
    if 'file_count' not in response or 'file_name' not in response:
        return None
    return (response['file_count'], response['file_name'])
    # get 响应状态：200, 结果：{"file_count":1,"file_name":["f9c855c8-2310-11f0-bfb9-ab84109023df_1745721626669_PART1.csv"]}

def mfi_file_download(ppid, requestid, filename):
    url = f'{mfiurl}/api/v1.0/external/authEntities/{requestid}/{filename}'
    headers = {"Accept": "application/json", "Content-Type": "application/json"}
    logger.info(url)
    code, file = mfi_api_http_get(url, headers)
    if code != 200:
        return None
    filepath = os.path.join(OUTPUT_BIN, ppid, datetime.now().strftime("%Y%m%d_%H%M"), filename)
    os.makedirs(os.path.dirname(filepath), exist_ok=True) 
    with open(filepath, 'w') as fd:
        firstline = 'Ppid,Token,Base64,Crc32\n'
        fd.write(firstline)
        fd.write(file)
    return filepath
    
def mfi_file_add_uuid_provisioning(ppid, filepath):    
    url = f'{mfiurl}/api/v1.0/external/bulk/usedAuthEntities'
    headers = {"Accept": "application/json", "Content-Type": "application/json"}
    body = {}
    body['ppid'] = ppid
    tokenuuid = {}
    uuidlist = []
    with open(filepath, "r") as fd:
        csvline = fd.readline()
        data = csvline.split(',')
        if 'Token' not in data:
            return None
        tpos = None
        for pos,one in enumerate(data):
            if one == 'Token':
                tpos = pos
        csvline = fd.readline()
        while (csvline):
            if len(csvline) < 10:
                csvline = fd.readline()
                continue
            data = csvline.split(',')
            print(data[tpos])
            uid = str(uuid.uuid4())
            tokenuuid[data[tpos]] = uid
            uuidlist.append(uid)
            csvline = fd.readline()
    body['auth_entities'] = []
    body['auth_entities'].append(tokenuuid)
    logger.info(body)
    # print(uuidlist)
    code, response = mfi_api_http_post(url, headers, body)
    if code != 202 or 'request_id' not in response:
        return None
    
    filedir = os.path.dirname(filepath)
    filename = os.path.basename(filepath)
    outfile = os.path.join(filedir, f'uuid-{filename}')
    
    with open(filepath, "r") as fr:
        with open(outfile, "w") as fw:
            csvline = fr.readline().replace('\n', ',Uuid,\n')
            fw.write(csvline)
            csvline = fr.readline()
            num = 0
            while (csvline):
                if len(csvline) < 10:
                    csvline = fr.readline()
                    continue
                csvline = csvline.replace('\n', f',{uuidlist[num]},\n')
                num = num + 1
                fw.write(csvline)
                csvline = fr.readline()
    os.remove(filepath)

def create_token(ppid, num):
    logger.info(cert_file)
    requestid = mfi_auth_entities_request(ppid, num)
    logger.info(requestid)
    if requestid == None:
        logger.error('get mfi_auth_entities_request fail')
        return
    files = mfi_file_name_request(requestid)
    logger.info(files)
    if files == None:
        logger.error('get mfi_file_name_request fail')
        return
    filelist = []
    for file in files[1]:
        file = mfi_file_download(ppid, requestid, file)
        if file:
            filelist.append(file)
    logger.info(filelist)
    for file in filelist:
        mfi_file_add_uuid_provisioning(ppid, file);

        

def main():
    init_logging()   # 仅调用一次
    arg_ls = len(sys.argv)
    if (arg_ls < 2 ):
        logger.info("Parameter error len: " + arg_ls)
        exit(-1)
    file = sys.argv[1]
    # download_app_packet(com=com, prodir=project, addr=addr)
    # base64_2_bin('C8CD34A9-F43C-F71E-2D9B-ADC52C2C93E5', "MYHAMFACAQECAQEESDBGAiEApD8SsLmH+VydVASVGNHZGme2zbBRH2ToeI5Wf4Q82k0CIQCL5bE1UF9P9TUfuUdfMQD3YU96Cwbaf1Lv56kaX4FE6jBsAgECAgEBBGQxYjAJAgFmAgEBBAECMBACAWUCAQEECJRFugOHAQAAMBECAgDKAgEBBAgAAAAAAAAACDAWAgIAyQIBAQQNMjM3NzY3LTg4MjYwNTAYAgFnAgEBBBCQLv7uF6lKM6pbY9WA4B9S")
    if (os.path.exists(file)):
        if file.endswith('.csv'):
            create_token_by_csv(file)
        elif file.endswith('.bin'):
            if arg_ls == 2:
                print_token_bin(file)
            else:
                print_token_bin(file, sys.argv[2])
    elif file == 'token':
        create_token(sys.argv[2], sys.argv[3])
    else:
        binstr = base64.b64decode(file) #.hex()
        if arg_ls == 2:
            for i,one in enumerate(binstr):
                print("%02X"%one, end='');
        elif sys.argv[2] == 'c':
            for i,one in enumerate(binstr):
                if (i % 0x10) == 15:
                    print("0x%02X,"%one);
                else:
                    print("0x%02X,"%one, end=' ')
        
        print(f" b64decode {len(binstr)}")

        

if __name__ == "__main__":
    main()



